/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

var zrUtil = require("static/plugins/js/zrender/lib/core/util");

var VisualMapping = require("./VisualMapping");

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @file Visual solution, for consistent option specification.
 */
var each = zrUtil.each;

function hasKeys(obj) {
    if (obj) {
        for (var name in obj) {
            if (obj.hasOwnProperty(name)) {
                return true;
            }
        }
    }
}
/**
 * @param {Object} option
 * @param {Array.<string>} stateList
 * @param {Function} [supplementVisualOption]
 * @return {Object} visualMappings <state, <visualType, module:echarts/visual/VisualMapping>>
 */

function createVisualMappings(option, stateList, supplementVisualOption) {
    var visualMappings = {};
    each(stateList, function (state) {
        var mappings = (visualMappings[state] = createMappings());
        each(option[state], function (visualData, visualType) {
            if (!VisualMapping.isValidType(visualType)) {
                return;
            }

            var mappingOption = {
                type: visualType,
                visual: visualData,
            };
            supplementVisualOption &&
                supplementVisualOption(mappingOption, state);
            mappings[visualType] = new VisualMapping(mappingOption); // Prepare a alpha for opacity, for some case that opacity
            // is not supported, such as rendering using gradient color.

            if (visualType === "opacity") {
                mappingOption = zrUtil.clone(mappingOption);
                mappingOption.type = "colorAlpha";
                mappings.__hidden.__alphaForOpacity = new VisualMapping(
                    mappingOption
                );
            }
        });
    });
    return visualMappings;

    function createMappings() {
        var Creater = function () {}; // Make sure hidden fields will not be visited by
        // object iteration (with hasOwnProperty checking).

        Creater.prototype.__hidden = Creater.prototype;
        var obj = new Creater();
        return obj;
    }
}
/**
 * @param {Object} thisOption
 * @param {Object} newOption
 * @param {Array.<string>} keys
 */

function replaceVisualOption(thisOption, newOption, keys) {
    // Visual attributes merge is not supported, otherwise it
    // brings overcomplicated merge logic. See #2853. So if
    // newOption has anyone of these keys, all of these keys
    // will be reset. Otherwise, all keys remain.
    var has;
    zrUtil.each(keys, function (key) {
        if (newOption.hasOwnProperty(key) && hasKeys(newOption[key])) {
            has = true;
        }
    });
    has &&
        zrUtil.each(keys, function (key) {
            if (newOption.hasOwnProperty(key) && hasKeys(newOption[key])) {
                thisOption[key] = zrUtil.clone(newOption[key]);
            } else {
                delete thisOption[key];
            }
        });
}
/**
 * @param {Array.<string>} stateList
 * @param {Object} visualMappings <state, Object.<visualType, module:echarts/visual/VisualMapping>>
 * @param {module:echarts/data/List} list
 * @param {Function} getValueState param: valueOrIndex, return: state.
 * @param {object} [scope] Scope for getValueState
 * @param {string} [dimension] Concrete dimension, if used.
 */
// ???! handle brush?

function applyVisual(
    stateList,
    visualMappings,
    data,
    getValueState,
    scope,
    dimension
) {
    var visualTypesMap = {};
    zrUtil.each(stateList, function (state) {
        var visualTypes = VisualMapping.prepareVisualTypes(
            visualMappings[state]
        );
        visualTypesMap[state] = visualTypes;
    });
    var dataIndex;

    function getVisual(key) {
        return data.getItemVisual(dataIndex, key);
    }

    function setVisual(key, value) {
        data.setItemVisual(dataIndex, key, value);
    }

    if (dimension == null) {
        data.each(eachItem);
    } else {
        data.each([dimension], eachItem);
    }

    function eachItem(valueOrIndex, index) {
        dataIndex = dimension == null ? valueOrIndex : index;
        var rawDataItem = data.getRawDataItem(dataIndex); // Consider performance

        if (rawDataItem && rawDataItem.visualMap === false) {
            return;
        }

        var valueState = getValueState.call(scope, valueOrIndex);
        var mappings = visualMappings[valueState];
        var visualTypes = visualTypesMap[valueState];

        for (var i = 0, len = visualTypes.length; i < len; i++) {
            var type = visualTypes[i];
            mappings[type] &&
                mappings[type].applyVisual(valueOrIndex, getVisual, setVisual);
        }
    }
}
/**
 * @param {module:echarts/data/List} data
 * @param {Array.<string>} stateList
 * @param {Object} visualMappings <state, Object.<visualType, module:echarts/visual/VisualMapping>>
 * @param {Function} getValueState param: valueOrIndex, return: state.
 * @param {number} [dim] dimension or dimension index.
 */

function incrementalApplyVisual(stateList, visualMappings, getValueState, dim) {
    var visualTypesMap = {};
    zrUtil.each(stateList, function (state) {
        var visualTypes = VisualMapping.prepareVisualTypes(
            visualMappings[state]
        );
        visualTypesMap[state] = visualTypes;
    });

    function progress(params, data) {
        if (dim != null) {
            dim = data.getDimension(dim);
        }

        function getVisual(key) {
            return data.getItemVisual(dataIndex, key);
        }

        function setVisual(key, value) {
            data.setItemVisual(dataIndex, key, value);
        }

        var dataIndex;

        while ((dataIndex = params.next()) != null) {
            var rawDataItem = data.getRawDataItem(dataIndex); // Consider performance

            if (rawDataItem && rawDataItem.visualMap === false) {
                continue;
            }

            var value =
                dim != null ? data.get(dim, dataIndex, true) : dataIndex;
            var valueState = getValueState(value);
            var mappings = visualMappings[valueState];
            var visualTypes = visualTypesMap[valueState];

            for (var i = 0, len = visualTypes.length; i < len; i++) {
                var type = visualTypes[i];
                mappings[type] &&
                    mappings[type].applyVisual(value, getVisual, setVisual);
            }
        }
    }

    return {
        progress: progress,
    };
}

exports.createVisualMappings = createVisualMappings;
exports.replaceVisualOption = replaceVisualOption;
exports.applyVisual = applyVisual;
exports.incrementalApplyVisual = incrementalApplyVisual;
